import { parse, transform } from '@swc/core';
import { Visitor } from '@swc/core/Visitor.js';
import type {
  CallExpression,
  ImportDeclaration,
  Program,
  Expression,
  ModuleItem,
  Statement,
  ImportSpecifier, // 添加 ImportSpecifier 导入
} from '@swc/types';

interface PluginOptions {
  fileRegex?: RegExp;
  importPrefix?: string;
  importPathHandler?: (path: string) => string;
}

type Span = {
  start: number;
  end: number;
  ctxt: number;
};

type ASTNode = {
  type: string;
  span: Span;
  [key: string]: any;
};

// 修改 TransformCacheEntry 接口，使 map 可选
interface TransformCacheEntry {
  code: string;
  map?: any;
}

class RequireTransformVisitor extends Visitor {
  // 改为 public 以便外部访问
  public static readonly DEFAULT_PREFIX = '_vite_plugin_require_';
  private static readonly STYLE_REGEX = /\.(css|less|sass|scss|styl|stylus)$/;
  private static readonly MAX_CACHE_SIZE = 100;

  private imports: Map<string, string> = new Map();
  private importCount = 0;
  private prefix: string;
  private pathHandler: (path: string) => string;
  public hasRequire: boolean = false; // 改为 public

  constructor(options: PluginOptions) {
    super();
    this.prefix = options.importPrefix || RequireTransformVisitor.DEFAULT_PREFIX;
    this.pathHandler =
      options.importPathHandler ||
      ((path: string) => path.replace(/(.*\/)*([^.]+).*/gi, '$2').replace(/-/g, '_'));
  }

  private getImportName(path: string): string {
    if (!path) {
      throw new Error('Import path cannot be empty');
    }

    if (this.imports.has(path)) {
      return this.imports.get(path)!;
    }

    const importName = `${this.prefix}${this.pathHandler(path)}_${this.importCount++}`;
    this.imports.set(path, importName);
    return importName;
  }

  private getRequirePath(expression: any): string {
    if (!expression) {
      throw new Error('Require path expression cannot be null or undefined');
    }

    switch (expression.type) {
      case 'StringLiteral':
        return expression.value;
      case 'TemplateLiteral':
        if (expression.expressions.length > 0) {
          throw new Error('Dynamic template expressions are not supported in require paths');
        }
        return expression.quasis[0].value.raw;
      case 'BinaryExpression':
        if (expression.operator === '+') {
          return this.getRequirePath(expression.left) + this.getRequirePath(expression.right);
        }
        throw new Error(`Unsupported binary operator: ${expression.operator}`);
      default:
        throw new Error(`Unsupported expression type: ${expression.type}`);
    }
  }

  private static createIdentifier(name: string, span: Span): ASTNode {
    return {
      type: 'Identifier',
      value: name,
      span,
      optional: false,
      ctxt: 0,
    };
  }

  private static createMemberExpression(object: ASTNode, property: string, span: Span): ASTNode {
    return {
      type: 'MemberExpression',
      object,
      property: RequireTransformVisitor.createIdentifier(property, span),
      span,
      optional: false,
    };
  }

  private static createBinaryExpression(
    left: ASTNode,
    operator: string,
    right: ASTNode,
    span: Span,
  ): Expression {
    // 修改返回类型为 Expression
    return {
      type: 'BinaryExpression',
      operator,
      left,
      right,
      span,
    } as Expression; // 添加类型断言
  }

  private static createImportSpecifier(
    type: 'default' | 'namespace',
    name: string,
    span: Span,
  ): ImportSpecifier {
    return {
      type: type === 'default' ? 'ImportDefaultSpecifier' : 'ImportNamespaceSpecifier',
      local: RequireTransformVisitor.createIdentifier(name, span) as any,
      span,
    };
  }

  private static createImportDeclaration(
    specifiers: ImportSpecifier[],
    source: string,
    span: Span,
  ): ImportDeclaration {
    return {
      type: 'ImportDeclaration',
      specifiers,
      source: {
        type: 'StringLiteral',
        value: source,
        span,
      },
      span,
      typeOnly: false,
    };
  }

  visitCallExpression(expr: CallExpression): Expression {
    if (expr.callee.type === 'Identifier' && expr.callee.value === 'require') {
      this.hasRequire = true;
      const arg = expr.arguments[0];
      if (
        arg.expression.type === 'StringLiteral' ||
        arg.expression.type === 'TemplateLiteral' ||
        arg.expression.type === 'BinaryExpression'
      ) {
        try {
          const path = this.getRequirePath(arg.expression);
          const importName = this.getImportName(path);
          const isStyle = RequireTransformVisitor.STYLE_REGEX.test(path);

          if (isStyle) {
            return {
              type: 'ObjectExpression',
              properties: [],
              span: expr.span,
            };
          } else {
            const memberExpr = RequireTransformVisitor.createMemberExpression(
              RequireTransformVisitor.createIdentifier(importName, expr.span),
              'default',
              expr.span,
            );
            return RequireTransformVisitor.createBinaryExpression(
              memberExpr,
              '||',
              RequireTransformVisitor.createIdentifier(importName, expr.span),
              expr.span,
            );
          }
        } catch (error) {
          console.warn(
            `[vite-plugin-require] Warning:`,
            error instanceof Error ? error.message : String(error),
          );
          return super.visitCallExpression(expr) as Expression;
        }
      }
    }
    return super.visitCallExpression(expr) as Expression;
  }

  visitProgram(program: Program): Program {
    const result = super.visitProgram(program);

    if (!this.hasRequire) {
      return program;
    }

    const imports: ImportDeclaration[] = [];
    for (const [path, name] of this.imports.entries()) {
      const isStyle = RequireTransformVisitor.STYLE_REGEX.test(path);
      imports.push(
        RequireTransformVisitor.createImportDeclaration(
          isStyle
            ? []
            : [RequireTransformVisitor.createImportSpecifier('namespace', name, result.span)],
          path,
          result.span,
        ),
      );
    }

    return {
      ...result,
      type: 'Module',
      body: [...imports, ...result.body],
    };
  }

  public static clearCacheIfNeeded(cache: Map<string, TransformCacheEntry>) {
    if (cache.size > RequireTransformVisitor.MAX_CACHE_SIZE) {
      const entriesToDelete = cache.size - RequireTransformVisitor.MAX_CACHE_SIZE;
      const keys = Array.from(cache.keys()).slice(0, entriesToDelete);
      keys.forEach((key) => cache.delete(key));
    }
  }
}

export default function vitePluginRequireTransform(options: PluginOptions = {}) {
  const { fileRegex = /\.(js|jsx|ts|tsx)$/ } = options;
  const transformCache = new Map<string, TransformCacheEntry>();

  return {
    name: options.importPrefix || RequireTransformVisitor.DEFAULT_PREFIX,
    async transform(code: string, id: string) {
      try {
        if (!fileRegex.test(id) || !code.includes('require')) {
          return null;
        }

        const cacheKey = `${id}:${code}`;
        if (transformCache.has(cacheKey)) {
          return transformCache.get(cacheKey);
        }

        const ast = await parse(code, {
          syntax: 'typescript',
          tsx: true,
        });

        const visitor = new RequireTransformVisitor(options);
        const result = visitor.visitProgram(ast);

        if (!visitor.hasRequire) {
          return null;
        }

        const output = await transform(result, {
          jsc: {
            target: 'es2020',
            parser: {
              syntax: 'typescript',
              tsx: true,
            },
          },
        });

        if (output) {
          transformCache.set(cacheKey, output);
          RequireTransformVisitor.clearCacheIfNeeded(transformCache);
          return output;
        }

        return null;
      } catch (error) {
        console.error(
          `[vite-plugin-require] Error transforming ${id}:\n`,
          error instanceof Error ? error.message : String(error),
          '\nCode snippet:',
          code.slice(0, 100) + '...',
        );
        return null;
      }
    },
  };
}
